// ---------------------------------------------------------------------------
//  File:ring_builder.h
//
//  Description: Contains the main routines that build the radius rings
//
//  Author: golden.colorado.5280@gmail.com
//
//  Date: December 26th, 2015
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
// ---------------------------------------------------------------------------


#include "ring_builder.h"
#include "color_support.h"  //color related algorithms

//Converts a number to its string representation
template <typename T>
    std::string to_string(T const& value,               //value being converted to a string
                          unsigned int precision) {     //number of decimal places to show in the string
    std::stringstream sstr;
    sstr << std::setprecision(precision) << std::fixed << value;
    return sstr.str();
}

//Inserts XML tags that make up a KML file header
static void kml_header(std::ofstream& kml_file, std::string kml_name){
    kml_file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << "\n";
    kml_file << "<kml xmlns=\"http://www.opengis.net/kml/2.2\" xmlns:gx=\"http://www.google.com/kml/ext/2.2\" xmlns:kml=\"http://www.opengis.net/kml/2.2\" xmlns:atom=\"http://www.w3.org/2005/Atom\">" << "\n";
    kml_file << "<Document>" << "\n";
    kml_file << " <name>" + kml_name + "</name>" << "\n";
}

//Inserts XML tags that make up the KML style definition. This block sets the
// color of the polygon outline, fill color, and opacity of each radius ring.
static void kml_style(std::ofstream& kml_file,
                      std::string style_name,
                      std::string line_color,std::string fill_color, double thickness){
    kml_file << "	<StyleMap id=\"" + style_name + "\">" << "\n";
    kml_file << "		<Pair>" << "\n";
    kml_file << "			<key>normal</key>" << "\n";
    kml_file << "			<styleUrl>#sn_ylw-pushpin</styleUrl>" << "\n";
    kml_file << "		</Pair>" << "\n";
    kml_file << "		<Pair>" << "\n";
    kml_file << "			<key>highlight</key>" << "\n";
    kml_file << "			<styleUrl>#sh_ylw-pushpin</styleUrl>" << "\n";
    kml_file << "		</Pair>" << "\n";
    kml_file << "	</StyleMap>" << "\n";
    kml_file << "	<Style id=\"" + style_name + "\">" << "\n";
    kml_file << "		<LineStyle>" << "\n";
    kml_file << "			<color>"+ line_color + "</color>" << "\n";
    kml_file << "			<width>"+ to_string(thickness,1) + "</width>" << "\n";
    kml_file << "		</LineStyle>" << "\n";
    kml_file << "		<PolyStyle>" << "\n";
    kml_file << "			<color>"+ fill_color + "</color>" << "\n";
    kml_file << "		</PolyStyle>" << "\n";
    kml_file << "	</Style>" << "\n";
}

//Inserts XML tags that make up a KML Folder. If there a multiple radius
// rings polygons,all the polygons will show up in one folder.
static void kml_folder(std::ofstream& kml_file){
    kml_file << " <Folder>" << "\n";
    kml_file << "		<name>Rings</name>" << "\n";
    kml_file << "		<open>1</open>" << "\n";
}

//Inserts XML tags that make up a KML file footer. If there a multiple radius
// rings polygons,all the polygons will show up in one folder.
static void kml_footer(std::ofstream& kml_file){
    kml_file << " </Folder>" << "\n";
    kml_file << "</Document>" << "\n";
    kml_file << "</kml>" << "\n";
}


static void build_placemark(std::ofstream& kml_file, std::string name, KMLPOINT point, std::string style_name){
    kml_file << "	<Placemark>" << "\n";
    kml_file << "       <name>" + name + "</name>" << "\n";
    kml_file << "       <styleUrl>#" + style_name + "</styleUrl>" << "\n";
    kml_file << " 		<Point>" << "\n";
    kml_file << "			<coordinates>" << "\n";
    kml_file << to_string(point.lon,GEO_PRECISION) << "," << to_string(point.lat,GEO_PRECISION) << "," << to_string(point.hgt,GEO_PRECISION) << " ";
    kml_file << "       </coordinates>" << "\n";
    kml_file << "		</Point>" << "\n";
    kml_file << "   </Placemark>" << "\n\n";
}


//Inserts XML tags that generates the actual radius ring polygon.
static void build_kml(std::ofstream& kml_file, std::string name, std::list<KMLPOINT> points, std::string style_name){

    kml_file << "	<Placemark>" << "\n";
    kml_file << "       <name>" + name + "</name>" << "\n";
    kml_file << "       <styleUrl>#" + style_name + "</styleUrl>" << "\n";
    kml_file << " 		<Polygon>" << "\n";
    kml_file << "			<tessellate>1</tessellate>" << "\n";
    kml_file << "			<outerBoundaryIs>" << "\n";
    kml_file << "			<LinearRing>" << "\n";
    kml_file << "			<coordinates>" << "\n";

    KMLPOINT point;

    while (!points.empty()){
        point = points.back();
        kml_file << to_string(point.lon,GEO_PRECISION) << "," << to_string(point.lat,GEO_PRECISION) << "," << to_string(point.hgt,GEO_PRECISION) << " ";
        points.pop_back();
    }

    kml_file << "       </coordinates>" << "\n";
    kml_file << "		</LinearRing>" << "\n";
    kml_file << "		</outerBoundaryIs>" << "\n";
    kml_file << "		</Polygon>" << "\n";
    kml_file << "   </Placemark>" << "\n\n";
}

//Computes the location of the radius rings
// Code Reuse:
//      This application uses geodesy algorithms from the open source library
//      geostarslib-0.9.3. This software is available at the following URL.
//      http://sourceforge.net/projects/geostarslib/files/geoStarLib-0.9.3/
static std::list<KMLPOINT> compute_points_along_ring(GEO_LOCATION observer, double range ){

    double aer_in[3];
    double efg_out[3];
    double lat,lon,hgt;
    KMLPOINT point;
    std::list<KMLPOINT> points;

    for( double azimuth = 0; azimuth < (2*PI+INTRVL); azimuth = azimuth + INTRVL ){
                aer_in[GEO_AZ] = azimuth;
                aer_in[GEO_EL] = 0;
                aer_in[GEO_RNG] = range * KM_TO_M;

                //Ingests Range, Azimuth, Elevation (RAE) and site info and returns the
                // earth centered Cartesian coordinates (E,F,G) coordinates that the
                // RAE points to. Taken from geostarslib-0.9.3.
                geoRae2Efg (&observer, aer_in, efg_out);

                //Convert earth centered Cartesian coordinates (E,F,G), into
                // geodetic coordinates (latitude, longitude, and ellipsoid height)
                // Taken from geostarslib-0.9.3.
                geoEfg2Llh (&observer, efg_out, &lat, &lon, &hgt);

                //Store the points into a structure
                point.lat = lat;
                point.lon = lon;
                point.hgt = hgt;

                //Store the structure instance into a list
                points.push_back(point);
    }

    //Return the list of points
    return(points);
}


//Main routine that builds the KML file
void multi_ring(double observer_lat, double observer_lon,   //Geographic coordinates for the center of the rings
                double range,                               //Distance from the center to the first ring (kilometers)
                double step_size,                           //Distance between each ring (kilometers)
                unsigned int total_rings,                   //Total rings to generate
                double opacity,                        //opacity of the fill color (0-1)
                RGB inner_color,                            //Color of the inner ring
                RGB outer_color,                            //Color of the outer ring
                RGB outline_color, double thickness,       //Color of the polygon outline
                std::string kml_name){

    //Define the center of the radius rings
    double observer_hgt = 0;    //rings will sit flat on the WGS-84 ellipsoid
    GEO_LOCATION observer;
    define_observer(&observer, observer_lat, observer_lon, observer_hgt);

    //Open KML File and start appending
    std::string kml_file_name =  kml_name + ".kml";
    std::ofstream kml_file;
    kml_file.open (kml_file_name.c_str(), std::ofstream::out);
    kml_header(kml_file,kml_name);

    //Color variables that will be updated in the loop below
    set_rgb_opacity(&inner_color,opacity/100.0);
    set_rgb_opacity(&outer_color,opacity/100.0);
    set_rgb_opacity(&outline_color,1);

    //Define variables to control the color of the radius rings
    std::string fill_kml_color, outline_kml_color;
    RGB blend;
    std::string style_name = "";
    outline_kml_color = rgb_to_kml(outline_color);

    //Insert new KML style for each ring. Note that this loop
    // smoothly transitions from the inner color to the outer color.
    for (unsigned int i = 0; i < total_rings; i = i + 1){
        style_name = "style" + to_string(i,0);

        //If there is only one ring don't attempt to blend the colors
        if (total_rings  > 1){
            blend = color_blend(&outer_color, &inner_color, (float)i/(total_rings-1)) ;
        }else{
            blend = inner_color;
        }

        fill_kml_color = rgb_to_kml(blend);
        kml_style(kml_file,style_name,outline_kml_color,fill_kml_color,thickness);
    }

    //Insert the kml folder tag
    kml_folder(kml_file);


    //List that will hold the ring points that will be written to the kml file
    std::list<KMLPOINT> points;
    std::string polygon_name = "";

    //Create each radius ring starting with the outer most ring.
    double ring_radius = range + (total_rings-1)*step_size;
    for (unsigned int i = 0; i < total_rings; i = i +1){

        //Compute the location of the points along the radius ring
        points = compute_points_along_ring(observer,ring_radius);

        //Add this ring to the kml file folder
        polygon_name = to_string(ring_radius,TITLE_PRECISION) + " km";
        style_name = "style" + to_string(i,0);
        build_kml(kml_file, polygon_name, points, style_name);

        //Clear the list of points for the next ring
        points.clear();

        //Increment the radius to draw the next ring. Distances are all in kilometers.
        ring_radius = ring_radius - step_size;
    }

    //Insert a label at the center of the rings; use the style of the center ring
    KMLPOINT center_point;
    center_point.hgt = observer_hgt;
    center_point.lat = observer_lat;
    center_point.lon = observer_lon;
    build_placemark(kml_file,kml_name, center_point,style_name);

    //Insert the last portion of the KML file
    kml_footer(kml_file);
    kml_file.close();
}
